package com.norriekoelle.virtualsensors.client.graphics;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;

import com.norriekoelle.virtualsensors.R;
import com.norriekoelle.virtualsensors.client.BookDatabaseAdapter;
import com.norriekoelle.virtualsensors.client.BookDatabaseAdapter.Collection;
import com.norriekoelle.virtualsensors.client.BookshelfActivity;
import com.norriekoelle.virtualsensors.shared.BookshelfObject;
import com.threed.jpct.Animation;
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Interact2D;
import com.threed.jpct.Light;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;

/**
 * The surface for displaying books in the UI.
 * 
 * @author Marion Koelle (marionkoelle)
 */
public class GLLayer implements GLSurfaceView.Renderer {

	private static final String LOG = "GLLayer";
	private BookshelfActivity master;
	private BookDatabaseAdapter dbAdapter;
	private long time = System.currentTimeMillis();
	private final int RESOLUTION = 128;
	private boolean stop = false;
	private FrameBuffer frameBuffer = null;
	private World world = null;
	private RGBColor back = new RGBColor(50, 50, 100);
	private Object3D middle = null;
	private Light sun = null;
	private enum FrameIndex {
		FIRST_FRAME, SECOND_FRAME, THIRD_FRAME, FOURTH_FRAME 
	};
	private int objectCount;
	// TODO(norrielm): Tidy these up.
	// Models.
	private Object3D[] book_key_1;
	private Object3D[] book_key_2;
	private Object3D[] book_key_3;
	private Object3D[] book_key_4;
	private Object3D[] paper;
	private boolean setUp = false;
	// TODO(norrielm): See if we can clean up the number of global variables here.
	private Object3D selected;
	private float touchTurn = 0;
	private float touchTurnUp = 0;
	private Matrix rotation;
	private float animate = 0.0f;
	private int fps = 0;
	private boolean playAnim = false;
	private GLFont glFont;
	public String displayMessage = "";
	public Collection currentBookshelfCategory = Collection.ALL;
	public float positionOfLast;
	private GLLayerStateManager manager;

	public GLLayer(BookshelfActivity master) {
		super();
		this.master = master;
		dbAdapter = master.getDBAdapter();
		book_key_1 = new Object3D[3];
		book_key_2 = new Object3D[3];
		book_key_3 = new Object3D[3];
		book_key_4 = new Object3D[3];
		paper = new Object3D[2];
		setUp = false;
		preloadMeshes();
		TextureManager tm = TextureManager.getInstance();		
		// Flush the texture before onResume is called.
		// TODO(norrielm): Find out exactly why: 'important, otherwise Exception when calling 
		// OnResume'
		tm.flush();
		System.gc();

		ArrayList<BookshelfObject> bookshelfObjects = dbAdapter.getAllObjects();		
		for (BookshelfObject bookshelfObject : bookshelfObjects) {
			String id = bookshelfObject.getDatabaseId();
			try {
				Drawable cover = Drawable.createFromStream((InputStream) 
					new URL(bookshelfObject.getDrawableResourceId()).getContent(), id);
				Texture temptex = new Texture(BitmapHelper.rescale(BitmapHelper.convert(cover), 
					RESOLUTION, RESOLUTION));
				temptex.setTextureCompression(true);
				temptex.compress();
				tm.addTexture(id, temptex);
				cover = null; 
				System.gc();
			} catch (MalformedURLException e){
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			MemoryHelper.compact();
		}
		Texture pages = new Texture(BitmapHelper.rescale(
				BitmapHelper.convert(master.getResources().getDrawable(R.drawable.pagestexture)), 
				RESOLUTION, RESOLUTION));
		pages.compress();
		tm.addTexture("pagestexture", pages);
		Texture background = new Texture(BitmapHelper.rescale(
				BitmapHelper.convert(master.getResources().getDrawable(R.drawable.background)), 
				256, 256));
		background.compress();
		tm.addTexture("background", background);
		// TODO(norrielm): See if we can factor this into a method.
		MemoryHelper.compact();
		tm.compress();	
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		try {
			if (!stop) {
				if (manager.atFront.contains(selected)) {
					Object3D selectedObject = selected;
					//Rotation
					if (getTouchTurn() != 0) {
						selectedObject.rotateY(getTouchTurn());
						setTouchTurn(0);
					}
					// TODO(norrielm): See if we can rename these methods more clearly.
					if (getTouchTurnUp() != 0) {
						selectedObject.rotateX(getTouchTurnUp());
						setTouchTurnUp(0);
					}
				}

				// Play an animation if the book is being opened.
				// TODO(norrielm): Find out whether playAnim will ever be true while selected is 
				// null.
				if (selected != null && playAnim == true) {
					// Speed of animation. 
					// TODO(norrielm): rename this var to speed of animation and remove comment 
					// above.
					animate += 0.004;
					// Usually > 1.0 for endless animations but we do not want interpolation to 
					// the first frame
					if (animate > 0.5f) {
						animate -= 0.5f;
						playAnim = false;
						master.startReading();
					}
					Object3D object = (Object3D) selected;
					object.animate(animate, 1);
				}
				frameBuffer.clear(back);
				Display display = ((WindowManager) master.getSystemService(Context.WINDOW_SERVICE))
				  .getDefaultDisplay();
				frameBuffer.blit(TextureManager.getInstance().getTexture("background"), display.getWidth(), display.getHeight(), 0, 0, 
						display.getWidth(), display.getHeight(), false);
				world.renderScene(frameBuffer);
				world.draw(frameBuffer);
				glFont.blitString(frameBuffer, currentBookshelfCategory.toString(), 20, 15, 60, RGBColor.WHITE);
				glFont.blitString(frameBuffer, displayMessage, 5, frameBuffer.getHeight()-5, 10, RGBColor.WHITE);
				frameBuffer.display();
				
//		    	gl.glRasterPos2f(20.0f, 15.0f);
//		    	glut.glutBitmapString(GLUT.BITMAP_HELVETICA_12,  currentBookshelfCategory.toString());

				// TODO(norrielm): rename time to currentTime.
				if ((System.currentTimeMillis() - time) >= 1000) {
					Logger.log(fps + "fps");
					fps = 0;
					time = System.currentTimeMillis();
				}
				fps++;
			} else if (frameBuffer != null) {
				frameBuffer.dispose();
				frameBuffer = null;
			}
			// TODO(norrielm): Find out what exception we're catching here and be more precise here. 
		} catch (Exception e) {
			Logger.log(e, Logger.MESSAGE);
		}
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		paint.setTypeface(Typeface.create((String)null, Typeface.BOLD));
		paint.setTextSize(16);
		glFont = new GLFont(paint);
		paint.setTextSize(50);
	}

	@Override
	public void onSurfaceChanged (GL10 gl,int w, int h) {
		if (frameBuffer != null) {
			frameBuffer.dispose();
		}
		MemoryHelper.compact();
		frameBuffer = new FrameBuffer(gl, w, h);
		if (master != null) {
			world = new World();
			world.setAmbientLight(20, 20, 20);
			sun = new Light(world);
			sun.setIntensity(200, 200, 200);
			ArrayList<BookshelfObject> objects = dbAdapter.getAllObjects();
			objectCount = 1;
			for (BookshelfObject object : objects) {
				String id = object.getDatabaseId();
				// Textures have already been loaded, so now we can start creating books.
				Object3D newCube;
				switch (object.getType()) {
				case BOOK:
					newCube = createBook(id, dbAdapter.getTitleforID(id), 
							dbAdapter.getAuthorforID(id), false);
					break;
				case PAPER: 
					newCube = createPaper(id, dbAdapter.getTitleforID(id), 
							dbAdapter.getAuthorforID(id));
					break;
				case BOOK_FULL_TEXTURE:
					newCube = createBook(id, dbAdapter.getTitleforID(id), 
							dbAdapter.getAuthorforID(id), true);
					break;
				default:		
					newCube = Primitives.getBox(10, 1);
					break;
				}
				newCube.setName(object.getDatabaseId());
				SimpleVector vec = new SimpleVector(objectCount*20, 0, 0);
				manager.positions[Integer.valueOf(object.getDatabaseId())] = vec;
				newCube.translate(vec);
				newCube.rotateY((float) ((3 * Math.PI / 2) + ((Math.PI / 4))));
				newCube.setRotationPivot(new SimpleVector(0, 0, 0));
				newCube.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
				if (rotation == null) {
					rotation = newCube.getRotationMatrix().cloneMatrix();
				}
				world.addObject(newCube);
				objectCount++;
			}

			// Create an invisible point to center the viewport.
			middle = Primitives.getCube(0.1f);
			middle.translate(objectCount/2*20, 0, 0);
			middle.setVisibility(false);

			// Set camera to look at the center.
			Camera cam = world.getCamera();
			cam.moveCamera(Camera.CAMERA_MOVEOUT, 120);
			cam.moveCamera(Camera.CAMERA_MOVERIGHT, objectCount/2*20);
			cam.lookAt(middle.getTransformedCenter());
			world.addObject(middle);
			sun.setPosition(cam.getPosition());
			world.buildAllObjects();

			// Now we are set up we can start to draw.
			setUp = true;
			try {
				finalize();
				// TODO(norrielm): Find out what's being thrown here and log it properly.
			} catch (Throwable e) {
				e.printStackTrace();
			}
			showCollection(master.getCurrentCollection());
		}
	}

	// TODO(norrielm): Find out when this is called. If onFinish then rename as appropriate.
	public void stop() {
		stop = true;
	}

	private Object3D createPaper(String coverTexture, String coverString, String authorString){
		Object3D paper = loadPaperModel (coverTexture, coverString, authorString);
		paper.scale(150.0f);
		paper.strip();
		paper.build();
		paper.compile();		
		return paper;
	}

	private Object3D createBook(String coverTexture, String coverString, String authorString, 
			boolean textured){
		Object3D book;
		Animation openAnim = new Animation (30);
		openAnim.createSubSequence("open");

		if (textured){
			// Load textured models.
			book = loadTexturedBookModel(FrameIndex.FIRST_FRAME, coverTexture, coverString, 
					authorString);
			openAnim.addKeyFrame(loadTexturedBookModel(FrameIndex.FIRST_FRAME, coverTexture, 
					coverString, authorString).getMesh());
			openAnim.addKeyFrame(loadTexturedBookModel(FrameIndex.FOURTH_FRAME, coverTexture, 
					coverString, authorString).getMesh());
		} else {
			// Load untextured models.
			book = loadBookModel(FrameIndex.FIRST_FRAME, coverTexture, coverString, authorString);
			openAnim.addKeyFrame(loadBookModel(FrameIndex.FIRST_FRAME, coverTexture, coverString, 
					authorString).getMesh());
			openAnim.addKeyFrame(loadBookModel(FrameIndex.FOURTH_FRAME, coverTexture, coverString, 
					authorString).getMesh());
		}
		book.scale(150.0f);
		book.setAnimationSequence(openAnim);
		book.build();
		return book;
	}

	/**
	 * Load a Mesh from multiple serialised files (created from one single .3ds file) and assign a 
	 * texture to the book cover.
	 * 
	 * @param frame key frame of the animation
	 * @param coverTextureName texture to be displayed as cover
	 */
	private Object3D loadBookModel (FrameIndex frame, String coverTextureName, String coverString, 
			String authorString){
		Object3D[] cubes = setKeyFrameBooksFromType(frame); 
		TextureManager tm = TextureManager.getInstance();
		if (tm.containsTexture(coverTextureName)) {
			cubes[2].setTexture(coverTextureName);
		} else {
			final int IMAGE_WIDTH = 256;
			final int IMAGE_HEIGHT = 256;
			Paint imagePaint = new Paint();
			imagePaint.setColor(Color.BLACK);
			imagePaint.setTextAlign(Align.CENTER);
			imagePaint.setTextSize(16f);

			Bitmap canvasBitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, 
					Bitmap.Config.ARGB_4444);
			Canvas imageCanvas = new Canvas(canvasBitmap);
			imageCanvas.drawColor(Color.WHITE);
			imageCanvas.drawText(coverString, IMAGE_WIDTH / 2, IMAGE_HEIGHT / 3,imagePaint);
			imageCanvas.drawText(authorString, IMAGE_WIDTH / 2,(int)(IMAGE_HEIGHT*0.5 / 3), 
					imagePaint);
			BitmapDrawable finalImage = new BitmapDrawable(canvasBitmap);

			Texture texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(finalImage), 
					256, 256));
			tm.addTexture(coverTextureName, texture);
			cubes[2].setTexture(coverTextureName);
			canvasBitmap.recycle();
			canvasBitmap = null;
		}
		cubes[0].setTexture("pagestexture");
//		cubes[0].setAdditionalColor(RGBColor.WHITE);
		Object3D book;
		book = Object3D.mergeObjects(cubes[0], cubes[1]);
		book = Object3D.mergeObjects(book, cubes[2]);
		book.calcBoundingBox();
		book.calcNormals();
		// Do not call build() or strip() here as this is called when we animate the object.
		return book;
	}

	private Object3D loadTexturedBookModel(FrameIndex frame, String coverTextureName, String coverString, 
			String authorString){
		Object3D[] cubes = setKeyFrameBooksFromType(frame);
		TextureManager tm = TextureManager.getInstance();
		if (tm.containsTexture(coverTextureName)) {
			cubes[1].setTexture(coverTextureName);
		}
		else {
			// TODO(marionkoelle): Text erstellen in Methode auslagern
		}
		cubes[0].setTexture("pagestexture");
//		cubes[0].setAdditionalColor(RGBColor.WHITE);

		Object3D book;
		book = Object3D.mergeObjects(cubes[0], cubes[1]);
		book.calcBoundingBox();
		book.calcNormals();
		return book;
	}

	private Object3D[] setKeyFrameBooksFromType(FrameIndex frame) {
		Object3D[] cubes = new Object3D[3];
		// TODO(norrielm): Create an enum to deal with the keyframes.
		switch (frame){
		case FIRST_FRAME:
			cubes [0] = book_key_1[0].cloneObject();
			cubes [1] = book_key_1[1].cloneObject();
			cubes [2] = book_key_1[2].cloneObject();
			break;
		case SECOND_FRAME:
			cubes [0] = book_key_2[0].cloneObject();
			cubes [1] = book_key_2[1].cloneObject();
			cubes [2] = book_key_2[2].cloneObject();
			break;
		case THIRD_FRAME:
			cubes [0] = book_key_3[0].cloneObject();
			cubes [1] = book_key_3[1].cloneObject();
			cubes [2] = book_key_3[2].cloneObject();
			break;
		case FOURTH_FRAME: 
			cubes [0] = book_key_4[0].cloneObject();
			cubes [1] = book_key_4[1].cloneObject();
			cubes [2] = book_key_4[2].cloneObject();
			break;
		}
		return cubes;
	}

	/** 
	 * Loads a Mesh from multipe serialised files (created from one single .3ds file)
	 * Assigns coverTexture to bookcover
	 * 
	 * @param type: keyframe of the animation
	 * @param coverTexture: texture to be displayed as cover
	 * @return Object3D
	 */
	private Object3D loadPaperModel (String coverTextureName, String coverString, 
			String authorString) {
		Object3D[] cubes = new Object3D[3];
		cubes[0] = paper[0].cloneObject();
		cubes[1] = paper[1].cloneObject();

		TextureManager tm = TextureManager.getInstance();
		if (tm.containsTexture(coverTextureName)) {
			cubes[1].setTexture(coverTextureName);
		} else {
			final int IMAGE_WIDTH = 256;
			final int IMAGE_HEIGHT = 256;
			Paint imagePaint = new Paint();
			imagePaint.setColor(Color.BLACK);
			imagePaint.setTextAlign(Align.CENTER);
			imagePaint.setTextSize(16f);

			Bitmap canvasBitmap = Bitmap.createBitmap(IMAGE_WIDTH, 
					IMAGE_HEIGHT, 
					Bitmap.Config.ARGB_4444);
			Canvas imageCanvas = new Canvas(canvasBitmap);
			imageCanvas.drawColor(Color.WHITE);
			imageCanvas.drawText(coverString, 
					IMAGE_WIDTH / 2, 
					IMAGE_HEIGHT / 3, 
					imagePaint);
			imageCanvas.drawText(authorString, 
					IMAGE_WIDTH / 2, 
					(int)(IMAGE_HEIGHT*0.5 / 3), 
					imagePaint); 
			BitmapDrawable finalImage = new BitmapDrawable(canvasBitmap);
			Texture texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(finalImage), 
					256, 256));
			tm.addTexture(coverTextureName, texture);
			cubes[1].setTexture(coverTextureName);
		}
		Object3D book = Object3D.mergeObjects(cubes[0], cubes[1]);
		book.calcBoundingBox();
		book.calcNormals();
		book.build();
		book.strip();
		book.compile();
		return book;
	}

	/**
	 * Avoid memory issues by loading meshes in advance and cloning these every instance so that we 
	 * only have to store them only once.
	 */
	private void preloadMeshes() {
		// TODO(norrielm): See if we can do this iteratively.
		book_key_1 [0] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_1_0)); 
		book_key_1 [1] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_1_1)); 
		book_key_1 [2] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_1_2));
		book_key_2 [0] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_2_0)); 
		book_key_2 [1] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_2_1)); 
		book_key_2 [2] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_2_2));
		book_key_3 [0] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_3_0)); 
		book_key_3 [1] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_3_1)); 
		book_key_3 [2] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_3_2));
		book_key_4 [0] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_4_0)); 
		book_key_4 [1] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_4_1)); 
		book_key_4 [2] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.book_animated_key_4_2));
		paper [0] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.paper_0)); 
		paper [1] = Loader.loadSerializedObject(master.getResources().openRawResource(
				R.raw.paper_1));
		System.gc();
	}

	// TODO(norrielm): Rename this to be something more sensible like playBookAnimation();
	public void showOpenAnimation() {
		playAnim = true;
	}

	public void zoomIn() {
		Camera cam = world.getCamera();
		cam.moveCamera(Camera.CAMERA_MOVEIN, 10);
	}

	public void zoomOut() {
		Camera cam = world.getCamera();
		cam.moveCamera(Camera.CAMERA_MOVEOUT, 10);	
	}

	public void moveCameraLeft(int distance) {
		if(setUp){
			Camera cam = world.getCamera();
			if (cam.getPosition().x > 0){
				middle.translate(-distance, 0, 0);
				sun.setPosition(cam.getPosition());
				cam.moveCamera(Camera.CAMERA_MOVELEFT, distance);
			}
		}
	}

	public void moveCameraRight(int distance) {
		if (setUp) {
			Camera cam = world.getCamera();
			if(cam.getPosition().x < positionOfLast) {//(objectCount-1)*40) {
				middle.translate(distance, 0, 0);
				cam.moveCamera(Camera.CAMERA_MOVERIGHT, distance);
				sun.setPosition(cam.getPosition());
			}
		}
	}

	public float getPositionOfFirst(Collection collection) {
		String firstId = collection.equals(Collection.ALL) ? 
				dbAdapter.getAllObjects().get(0).getDatabaseId() : 
				dbAdapter.getAllObjects(collection.ordinal()).get(0).getDatabaseId();
		Object3D first = world.getObjectByName(firstId);
		return first.getTransformedCenter().x;
	}
	

	public void setVisibility(boolean doDisplayBook) {
		setVisibility(doDisplayBook, selected);
	}
	
	private void setVisibility(boolean doDisplayBook, Object3D book) {
		book.setTransparencyMode(Object3D.TRANSPARENCY_MODE_ADD);
		Log.d(LOG, "transparency currently " + book.getTransparency());
		book.setTransparency(doDisplayBook ? (book.getTransparency() == -10 ? 0 : -10) :
			(book.getTransparency() == -10 ? 10 : 0));
	}
	
	public float getPositionOfLast(Collection collection) {
		String firstId = //collection.equals(Collection.ALL) ? 
				dbAdapter.getAllObjects().get(dbAdapter.getAllObjects().size()-1).getDatabaseId(); 
				//dbAdapter.getAllObjects(collection.ordinal()).get(dbAdapter.getAllObjects(collection.ordinal()).size()-1).getDatabaseId();
		Object3D first = world.getObjectByName(firstId);
		return first.getTransformedCenter().x;
	}
	
	/**
	 * Determine which object has been selected.
	 */
	public Object3D getObjectAtPosition(int x, int y) {	
		Camera camera = world.getCamera();
		SimpleVector direction = Interact2D.reproject2D3DWS(camera, frameBuffer, x, y).normalize();
		Object[] result = world.calcMinDistanceAndObject3D(camera.getPosition(), direction, 
			10000);
		selected = (Object3D) result[1];
		return selected;
	}

	// TODO(norrielm): Find out what touch turn actually means.
	public void setTouchTurn(float touchTurn) {
		this.touchTurn = touchTurn;
	}

	public float getTouchTurn() {
		return touchTurn;
	}

	public void setTouchTurnUp(float touchTurnUp) {
		this.touchTurnUp = touchTurnUp;
	}

	public float getTouchTurnUp() {
		return touchTurnUp;
	}

	public void bringSelectedToFront() {
		Object3D selectedObject = selected;
		Camera cam = world.getCamera();
		// TODO(marionkoelle): Insert Animation
		selectedObject.translate(0, 0, -40);
		selectedObject.rotateY((float) ((-3*Math.PI/2) - ((Math.PI/4))));
		selectedObject.setRotationPivot(new SimpleVector(0, 0, 0));

		float speed = cam.getPosition().x - selectedObject.getTransformedCenter().x;
		if (selectedObject.getCenter().x < cam.getPosition().x){
			cam.moveCamera(Camera.CAMERA_MOVELEFT, speed);
		} else if (selectedObject.getCenter().x > cam.getPosition().x){
			cam.moveCamera(Camera.CAMERA_MOVERIGHT, speed);
		}
		sun.setPosition(cam.getPosition());
		manager.atFront.add(selectedObject);
	}

	public void selectedToBack(){
		Object3D selectedObject = selected;
		selectedObject.clearTranslation();
		// TODO(marionkoelle): Insert Animation
		selectedObject.translate(manager.positions[Integer.parseInt(selectedObject.getName())]);
		selectedObject.getRotationMatrix().setTo(rotation.cloneMatrix());
		selectedObject.setRotationPivot(new SimpleVector(0, 0, 0));
		manager.atFront.remove(selectedObject);
	}

	public void attachStateManager(GLLayerStateManager manager){
		this.manager = manager;
	}

	public void moveObject(int x, int y) {
		Object3D selectedObject = selected;
		Camera cam = world.getCamera();
		if (selectedObject != null) {
			SimpleVector pos = getWorldPosition(x,y);
			if (pos != null) {
				float transX = (pos.normalize().x*100) + (cam.getPosition().x - 
					manager.positions[Integer.parseInt(selectedObject.getName())].x);
				float transY = (pos.normalize().y*100) + (cam.getPosition().y - 
					manager.positions[Integer.parseInt(selectedObject.getName())].y);
				selectedObject.clearTranslation();
				selectedObject.translate(
					manager.positions[Integer.parseInt(selectedObject.getName())]);
				selectedObject.translate(transX, transY, 0);
				selectedObject.setRotationPivot(new SimpleVector(0, 0, 0));
				
				manager.positions[Integer.parseInt(selectedObject.getName())] = selectedObject.getTransformedCenter();
			}
		}
	}

	private SimpleVector getWorldPosition(int x, int y) {
		SimpleVector pos = null;
		SimpleVector ray = Interact2D.reproject2D3DWS(world.getCamera(), frameBuffer, x, y);
		if (ray != null) {
			SimpleVector norm = ray.normalize();
			float f = world.calcMinDistance(world.getCamera().getPosition(), norm, 120);
			pos = new SimpleVector();
			SimpleVector offset = new SimpleVector(norm);
			norm.scalarMul(f);
			norm = norm.calcSub(offset);
			pos.add(norm);
		}
		return pos;
	}

	public void showCollection(Collection collectionToShow){
		if (setUp) {
			Camera cam = world.getCamera();
			ArrayList<BookshelfObject> objects = dbAdapter.getAllObjects();
			for (BookshelfObject object  : objects) {
				String bookId = object.getDatabaseId();
				Collection collectionOfBook = dbAdapter.getCollectionforID(bookId);
				Object3D bookInWorld = world.getObjectByName(bookId);
				// Display if it's in the collection we want to show or we're showing all.
				boolean doDisplayBook = collectionOfBook.equals(collectionToShow) || 
					collectionToShow.equals(Collection.ALL);
				setVisibility(doDisplayBook, bookInWorld);
			}
			positionOfLast = getPositionOfLast(collectionToShow);
			sun.setPosition(cam.getPosition());
			currentBookshelfCategory = collectionToShow;
		}
	}

	public void addSelectedObjectPosition() {
		Object3D selectedObject = selected;
		manager.positions[Integer.parseInt(selectedObject.getName())] = 
			selectedObject.getTransformedCenter();
	}

	public void unloadAllTextures() {
		TextureManager textureManager = TextureManager.getInstance();
		frameBuffer.freeMemory();
		ArrayList<BookshelfObject> bookshelfObjects = dbAdapter.getAllObjects();
		for (BookshelfObject bookshelfObject  : bookshelfObjects) {
			String databaseId = bookshelfObject.getDatabaseId();
			try {
				textureManager.removeAndUnload(databaseId, frameBuffer);				
			} catch(RuntimeException e) {
				Log.e(LOG, "Unload texture failed " + e.toString());
			}

		}
	}

	/**
	 * Set the log message that is displayed on the overlay display.
	 */
	public void setDisplayMessage(String message) {
		displayMessage = message;
	}
}
